Domain-Driven Design: Tactical Modeling
Identify subdomains and bounded contexts for the Library Management System, then model Entities and Value Objects using PlantUML class diagrams.
Activity Overview
This activity has two parts. In Part 1, you will study the full subdomain map of a Library Management System, understand why each subdomain is classified as Core, Supporting, or Generic, and see how subdomains map onto Bounded Contexts.
In Part 2, you will zoom into three Bounded Contexts , Member Management, Loan Management, and Collection Management: and perform tactical modeling: identifying Entities, Value Objects, and drawing PlantUML class diagrams. One example (Member Management) is already solved for you as a template.
A completed subdomain/context map (Part 1), and three PlantUML class diagrams for the Member, Loan, and Collection contexts (Part 2). These diagrams will be used directly in Activity 4, where you implement the domain and persistence layers.
Learning Goals
- Distinguish between a subdomain (problem space) and a bounded context (solution space).
- Classify subdomains as Core, Supporting, or Generic and justify the classification.
- Identify Entities and Value Objects within a bounded context.
- Model inheritance between related Entities using a common supertype.
- Draw a class diagram in PlantUML that reflects a domain model.
- Justify design decisions such as identity, immutability, and aggregate boundaries.
Prerequisites
- Chapter 2 , Domain-Driven Design Fundamentals (Entities, Value Objects, Aggregates, Bounded Contexts).
- Access to a PlantUML renderer ( IntelliJ plugin, VS Code PlantUML extension, or plantuml.com online editor).
- This is mainly a software modeling exercise.
Before You Start: Subdomain vs. Bounded Context
These two terms are often confused, but they describe different things:
| Concept | Space | Definition |
|---|---|---|
| Subdomain | Problem space | A natural division of the business itself. It exists whether or not software is written. Classified as Core, Supporting, or Generic. |
| Bounded Context | Solution space | An explicit boundary in the software model where the Ubiquitous Language has one precise meaning. Ideally maps 1:1 to a subdomain. |
The word "Book" refers to the same real-world concept, but it means something
different depending on the context. In the Collection Context, a Book is
a rich object with ISBN, author, and shelf status. In the Loans Context,
a Book is just a BookId reference. Same subdomain concept, different
bounded meaning.
What is PUML?
PlantUML (PUML) is a text-based tool for creating diagrams. You write a simple text description of your diagram, and PlantUML renders it as an image. It is widely used in software engineering to create UML diagrams, including class diagrams, sequence diagrams, and more.
Follow these to add PUML support in IntelliJ IDEA:
- Open IntelliJ IDEA and go to File > Settings (or IntelliJ IDEA > Preferences on macOS).
- In the Settings/Preferences dialog, navigate to Plugins.
- Click on the Marketplace tab and search for "PlantUML Integration".
- Click the Install button next to the plugin.
- Restart IntelliJ IDEA to activate the plugin.
Part 1: Subdomain and Bounded Context Map
The table below presents the full subdomain map for the Library Management System. Study each row and the reasoning behind its classification.
| Subdomain | Type | Why |
|---|---|---|
| Loan Management (circulation) | Core | This is the actual value the library delivers. Lending policies, due dates, and overdue rules are where the business logic and differentiation live. |
| Collection Management (catalog) | Supporting | Necessary to support lending , you cannot lend what is not cataloged , but managing a catalog is not itself a competitive differentiator. |
| Member Management | Supporting | Needed to know who is allowed to borrow, but membership record-keeping is not unique to this business. |
| Notifications (due-date reminders, overdue alerts) | Generic | A solved problem. Could be outsourced entirely to an email/SMS provider. |
| Authentication & Access Control | Generic | Login and permissions are almost never custom-built , typically handled by Spring Security or an OAuth provider. |
| Reporting & Analytics | Generic | Usage statistics and popular-title reports are typically handled with off-the-shelf BI tools. |
| Fines & Payments | Generic (arguably Supporting) | Payment processing is commoditized (e.g., Stripe), even though the business rule of when a fine applies is closer to core logic. A good discussion point , classification can depend on business context. |
Not every bounded context needs deep tactical modeling. Notifications, Authentication, and Reporting are thin wrappers around off-the-shelf capabilities , they do not need rich Entities or Aggregates. Rich tactical modeling is reserved for contexts tied to Core or Supporting subdomains, which is why Part 2 of this activity focuses only on Members, Loans, and Collection.
1 Task , Justify the Map
In pairs, answer the following in writing (3–4 sentences total):
- Do you agree that Fines & Payments is Generic rather than Supporting? Defend your position.
- Suppose the library wants to become known for an excellent personalized recommendation engine, meaning it will use machine learning to suggest books to members based on their borrowing history. Which subdomain would move to Core, and why?
Part 2: Tactical Modeling with PlantUML
We now zoom into the three Bounded Contexts most relevant to lending: Member Management, Loan Management, and Collection Management. For each, you will identify the Entities and Value Objects, then express them as a PlantUML class diagram.
| Context | Aggregate Root | Modeling Challenge |
|---|---|---|
| Member Management | Member |
Solved for you, use as the template. |
| Loan Management | Loan |
Single Entity with supporting Value Objects, reinforces the pattern. |
| Collection Management | LibraryItem (abstract) |
Multiple concrete Entities (Book, AudioMaterial, VideoMaterial) sharing a common supertype , introduces generalization. |
1 Worked Example: Member Management Context
The Member Management context is fully solved below. Study it carefully. You will follow the same reasoning for Loan and Collection. You can find the PlantUML code that draws the UML diagram for the Member Management Context written in Appendix A.
| Element | Kind | Reasoning |
|---|---|---|
Member | Entity (Aggregate Root) | Has a persistent identity (memberId) that matters regardless of attribute changes (e.g., name change, tier upgrade). |
MemberId | Value Object | Needs generation logic (e.g. UUID) and validation (non-null, correct format). A raw String/Long cannot carry that behavior. |
MemberName | Value Object | Composed of firstName/lastName; validated (non-empty), immutable. |
Email | Value Object | Defined entirely by its string value; validated (format) and immutable. |
MembershipTier | Value Object (Enum-like) | STANDARD or PREMIUM: a constrained classification, not a thing with identity. |
activeLoanCount | Simple attribute (int) | No validation rules or independent behavior : just a counter the Entity manages internally. |
status | Simple attribute (enum) | A plain classification flag with no behavior of its own; kept as a raw enum rather than a VO wrapper. |
registrationDate | Simple attribute (LocalDate) | No validation or behavior beyond being a date : a primitive type is sufficient. |
Below is the UML diagram for the Member Management Context. You can find the PlantUML code that draws the UML diagram for the Member Management Context written in Appendix A.
Not every field became a Value Object. activeLoanCount, status, and
registrationDate are simple attributes because they carry no validation rules or
independent behavior : they are just data the Entity tracks. Compare this to MemberId
and Email, which are promoted to Value Objects specifically because they need
generation or validation logic. This is the tactical test to apply: does this field have
rules or behavior of its own? If yes → Value Object (implemented as a POJO). If no → keep it
a simple attribute.
The composition arrow (*--) shows that Member owns its Value
Objects : they cannot exist independently outside a Member. Also notice that
only Member is stereotyped <<Aggregate Root>>; the
Value Objects are not entities and have no identity field of their own.
2 Your Task: Collection Management Context
The Collection Management context is more interesting: the library lends more than just
books. It also lends audio and video materials, which share some common fields (title, publication year, status) but also have their own unique fields.
So, how should we model this in a domain-driven way?
Concepts that you have learned in OOP (inheritance, polymorphism) are useful here,
but we must also consider DDD tactical modeling rules: identity, immutability, and aggregate boundaries.
Instead of modeling three unrelated aggregates (Book, AudioMaterial, VideoMaterial), model a single Aggregate Root
supertype called LibraryItem, with three concrete subtypes.
We will also make LibraryItem abstract, so that students cannot instantiate a generic library item that is neither a book, an audio item, nor a video.
This is a common pattern in DDD: a supertype carries the shared fields and behavior, while each subtype adds only what makes it distinct.
This context is an example that has a mix of Entities and Value Objects. The table below lists the elements, their kind, and reasoning for each.
| Element | Kind | Notes |
|---|---|---|
LibraryItem | Entity (abstract Aggregate Root) | Common fields: itemId, title, status, publicationYear. |
Book | Entity (concrete subtype) | Adds isbn, author. |
AudioMaterial | Entity (concrete subtype) | Adds duration, narrator. |
VideoMaterial | Entity (concrete subtype) | Adds duration, format, ageRating. |
ISBN, Author, Duration, PublicationYear, MaterialFormat | Value Objects | Shared or subtype-specific, all immutable. |
Why should LibraryItem be abstract rather than a concrete class students
instantiate directly? Think about what it would mean to create a "generic" library item
that is neither a book, an audio item, nor a video.
This is the same tactical modeling skill you used for Member and Loan, identify identity vs. attributes, but now applied across a family of related Entities. The supertype carries what all library items share; each subtype adds only what makes it distinct.
Build the UML diagram for this context using PUML following the example of the member context.
Use <|-- for the generalization (inheritance) arrows.
3 Your Task: Loan Management Context
Using the reasoning from the Member Management example, analyze the elements below. Note how business invariants (e.g., maximum renewals allowed, valid date intervals, non-negative monetary amounts) determine whether an attribute is modeled as a simple primitive or encapsulated inside a Value Object.
| Element | Kind | Reasoning (fill in or study) |
|---|---|---|
Loan |
Entity (Aggregate Root) |
Possesses a unique lifecycle and identity (loanId). Enforces critical domain invariants
across borrowing, renewals, and returns.
|
LoanId |
Value Object | Needs format validation (e.g., non-null UUID/prefix) and factory generation logic. |
borrowerId |
Simple attribute (MemberId reference) |
Cross-context reference to the borrowing Member. Held by identity only,
Loan never holds the full Member object.
|
iemId |
Simple attribute (ItemId reference) |
Cross-context reference to the borrowed LibraryItem. Held by identity only,
Loan never holds the full Book/LibraryItem object.
|
LoanPeriod |
Value Object |
Encapsulates temporal rules: dueDate must be after startDate,
and calculates whether an item is currently overdue.
|
RenewalCount |
Value Object | Guards the business invariant: a loan can be renewed at most 2 times. Increments itself and throws a domain exception if the threshold is exceeded. |
Money |
Value Object | Represents monetary amounts (e.g., daily overdue rates and accrued fines). Enforces non-negative values and safe arithmetic with currency. |
status |
Simple attribute (enum) |
State flag (ACTIVE, RETURNED, OVERDUE, LOST)
managed directly by the aggregate's transition methods.
|
Loan belongs to the Loan Management Bounded Context. It must never
hold a direct reference to the full Member or Book/LibraryItem entity objects.
Instead, it stores only their identity references (MemberId, ItemId).
Notice how modeling RenewalCount as a Value Object ensures that the rule
"no loan can be renewed more than twice" is validated at the point of creation/increment,
rather than relying on scattering raw int comparisons throughout your service layer.
Build the UML diagram using PUML as you did for Member and Collection. Use *-- for composition arrows and <|-- for generalization arrows.
Deliverables Checklist
Before You Finish , Check All Items
- Written justification for the Fines & Payments classification (Part 1).
- Written answer identifying which subdomain would move to Core under the recommendation-engine scenario (Part 1).
- Completed PlantUML diagram for the Collection Management context, including generalization arrows.
- Completed PlantUML diagram for the Loan Management context, rendered successfully.
Use the Forum on Moodle to answer this question:
Submit the PUML code and the rendered diagram on Moodle:
Troubleshooting Guide
| Problem | Likely Cause | Solution |
|---|---|---|
| PlantUML diagram does not render | Missing @startuml/@enduml tags or a syntax typo. |
Check that both tags are present and every class block is closed with }. |
| Unsure if something is an Entity or a Value Object | Confusing "has attributes" with "has identity." | Ask: "Does it matter WHICH one it is, or just WHAT it is?" If which , Entity. If what , Value Object. |
Tempted to give Loan a reference to the full Member object |
Forgetting the Bounded Context boundary. | Replace it with MemberId. Cross-context references must be by ID only. |
Not sure whether LibraryItem should be abstract |
Uncertainty about generalization in DDD. | An abstract class cannot be instantiated on its own, which mirrors reality: every real item is specifically a Book, Audio, or Video , never a bare "LibraryItem." |
Reflection Questions
Answer the following questions in your own words:
- What is the difference between a subdomain and a bounded context? Use one example from the Library system.
- Why are Notifications, Authentication, and Reporting modeled as "thin" bounded contexts rather than given rich domain models?
- Why does
LoanreferenceBookIdandMemberIdinstead of the fullBookandMemberobjects? - Why is
LibraryItemmodeled as an abstract Entity rather than a concrete one? - Which Value Objects in your Loan and Collection diagrams could be reused across other contexts, and which are context-specific?
Up Next
Great work! You now have a complete subdomain/context map and three PlantUML class diagrams for the Members, Loans, and Collection contexts.
In Activity 3, you will bring these diagrams to life: implementing the Entities and Value Objects as plain Java domain classes, writing Repository interfaces for each Aggregate Root, and connecting them to a Spring Data JPA persistence layer, exactly as described in Chapter 2.
Appendix A: Member Management Context
Bellow is the PlantUML code for the Member Management Context diagram:
@startuml
title Member Management Context
skinparam class {
BackgroundColor<> #FFE0B2
BorderColor<> #E65100
BackgroundColor<> #C8E6C9
BorderColor<> #2E7D32
BackgroundColor<> #B3E5FC
BorderColor<> #01579B
}
class Member <> {
- memberId : MemberId
- name : MemberName
- email : Email
- tier : MembershipTier
- status : MemberStatus
- activeLoanCount : int
- registrationDate : LocalDate
--
+ canBorrow() : boolean
+ incrementLoanCount() : void
+ decrementLoanCount() : void
+ suspend() : void
+ reinstate() : void
}
class MemberId <> {
- value : UUID
--
+ generate() : MemberId
}
class MemberName <> {
- firstName : String
- lastName : String
--
+ fullName() : String
}
class Email <> {
- value : String
}
enum MembershipTier {
STANDARD
PREMIUM
}
enum MemberStatus {
ACTIVE
SUSPENDED
}
Member *-- MemberId
Member *-- MemberName
Member *-- Email
Member *-- MembershipTier
Member *-- MemberStatus
legend right
|= Color |= Element |
|<#FFE0B2> | **Aggregate Root** |
|<#C8E6C9> | **Entity** |
|<#B3E5FC> | **Value Object** |
|<#F5F5F5> | **Enumeration** |
--
Aggregate Root:
The entry point to an aggregate and the only
object referenced from outside the aggregate.
--
Value Object:
An immutable object defined by its values,
without an independent identity.
endlegend
@enduml